package com.liuyi.framework.web;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.liuyi.framework.web.encrypt.CryptoRequestException;
import com.liuyi.tools.kits.RequestKit;
import com.liuyi.tools.web.resp.Response;
import com.liuyi.tools.web.resp.ResponseCall;
import com.liuyi.tools.web.unusual.BizEnum;
import com.liuyi.tools.web.unusual.BizException;
import com.liuyi.tools.web.unusual.HttpStatusBizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * @author Mr.fmy
 * @version 1.0.0
 * @ClassName ExceptionalHandler
 * @Description TODO web全局异常处理
 * @createTime 2019/9/16 14:03
 */
@ControllerAdvice
@Slf4j
public class WebExceptionalHandler {

    @Value("${spring.profiles.active:dev}")
    private String profiles;
    private final Set<ExceptionHandler> exceptionHandler = new HashSet<>(8);

    public WebExceptionalHandler() {
        exceptionHandler.add(new HttpStatusBizHandler());
        exceptionHandler.add(new BizHandler());
        exceptionHandler.add(new NoHandlerFoundHandler());
        exceptionHandler.add(new MaxUploadSizeExceededHandler());
        exceptionHandler.add(new ConstraintViolationHandler());
        exceptionHandler.add(new BindHandler());
        exceptionHandler.add(new MethodArgumentNotValidHandler());
    }

    public void set(ExceptionHandler... handler) {
        if (handler != null && handler.length > 0) {
            exceptionHandler.addAll(CollUtil.newArrayList(handler));
        }
    }

    public void remove(Class<ExceptionHandler> tClass) {
        ExceptionHandler remove = null;
        for (ExceptionHandler handler : exceptionHandler) {
            if (tClass.isInstance(handler)) {
                remove = handler;
                break;
            }
        }
        if (remove != null) {
            exceptionHandler.remove(remove);
        }
    }

    @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity<Response<Void>> exception(Exception e) {
        ExceptionHandler excuHandler = null;
        for (ExceptionHandler handler : exceptionHandler) {
            if (handler.supper(e)) {
                excuHandler = handler;
                break;
            }
        }
        if (excuHandler != null) {
            if (excuHandler.supperLog(e)) {
                excuHandler.log(e);
            }
            excuHandler.unifiedHandle(e);
            return excuHandler.handler(e);
        } else {
            printLog(e);
            if (profiles.equals("dev")) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ResponseCall.error(BizEnum.SERVER_ERROR, e.getMessage()));
            }
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ResponseCall.error(BizEnum.SERVER_ERROR));
        }
    }

    /**
     * 异常处理接口
     */
    public interface ExceptionHandler {

        /**
         * 是否支持
         */
        boolean supper(Exception ex);

        /**
         * 是否打印日志
         */
        default boolean supperLog(Exception ex) {
            return false;
        }

        /**
         * 统一额外处理
         */
        default void unifiedHandle(Exception ex) {

        }

        default void log(Exception ex) {
            printLog(ex);
        }

        /**
         * 处理方法
         */
        ResponseEntity<Response<Void>> handler(Exception ex);
    }

    class CryptoRequestBizHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof CryptoRequestException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            CryptoRequestException e = (CryptoRequestException) ex;
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error(e.getIEnum(), e.getMessage(), e.getArgs()));
        }
    }

    class HttpStatusBizHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof HttpStatusBizException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            HttpStatusBizException e = (HttpStatusBizException) ex;
            return ResponseEntity.status(e.getHttpStatus()).body(ResponseCall.error(e.getIEnum(), e.getMessage(), e.getArgs()));
        }
    }

    class BizHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof BizException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            BizException e = (BizException) ex;
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error(e.getIEnum(), e.getMessage(), e.getArgs()));
        }
    }

    class NoHandlerFoundHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof NoHandlerFoundException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            NoHandlerFoundException e = (NoHandlerFoundException) ex;
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseCall.error(BizEnum.NOT_FOUND_ERROR, "404 URL：" + e.getRequestURL()));
        }
    }

    class MaxUploadSizeExceededHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof MaxUploadSizeExceededException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            MaxUploadSizeExceededException e = (MaxUploadSizeExceededException) ex;
            if (e.getCause().getCause() instanceof SizeLimitExceededException) {
                Object[] args = new Object[]{((SizeLimitExceededException) e.getCause().getCause()).getPermittedSize() / 1024 / 1024 + "MB"};
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error("上传文件最大不可超过：{0}", args));
            }
            if (e.getCause().getCause() instanceof FileSizeLimitExceededException) {
                Object[] args = new Object[]{((FileSizeLimitExceededException) e.getCause().getCause()).getPermittedSize() / 1024 / 1024 + "MB"};
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error("上传文件最大不可超过：{0}", args));
            }
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error(ex.getMessage()));
        }
    }

    /**
     * 参数效验异常 在 controller加入 @Validated注解 并在参数上使用 例如 @NotNull注解时使用，一般针对单参数
     */
    class ConstraintViolationHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof ConstraintViolationException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            Map<String, Object> resultMap = new HashMap<>();
            ConstraintViolationException e = (ConstraintViolationException) ex;
            Set<ConstraintViolation<?>> exceptions = e.getConstraintViolations();
            exceptions.forEach(item -> {
                String queryParamPath = item.getPropertyPath().toString();
                String queryParam = queryParamPath.contains(".") ?
                        queryParamPath.substring(queryParamPath.indexOf(".") + 1) :
                        queryParamPath;
                resultMap.put(queryParam, item.getMessage());
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error(BizEnum.PARAM_VALID, JSONUtil.toJsonStr(resultMap)));
        }
    }

    /**
     * 参数效验异常 在 controller加入 @Validated注解 在方法内对实体类 @Valid 即可使用
     */
    class BindHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof BindException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            Map<String, Object> resultMap = new HashMap<>();
            BindException e = (BindException) ex;
            BindingResult result = e.getBindingResult();
            for (FieldError item : result.getFieldErrors()) {
                resultMap.put(item.getField(), item.getDefaultMessage());
            }
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error(BizEnum.PARAM_VALID, JSONUtil.toJsonStr(resultMap)));
        }
    }

    /**
     * 参数效验异常 在 controller加入 @Validated注解 在方法内对实体类 @Valid 以及 @RequestBody 即可使用
     */
    class MethodArgumentNotValidHandler implements ExceptionHandler {

        @Override
        public boolean supper(Exception ex) {
            return ex instanceof MethodArgumentNotValidException;
        }

        @Override
        public ResponseEntity<Response<Void>> handler(Exception ex) {
            Map<String, Object> resultMap = new HashMap<>();
            MethodArgumentNotValidException e = (MethodArgumentNotValidException) ex;
            BindingResult result = e.getBindingResult();
            for (FieldError item : result.getFieldErrors()) {
                resultMap.put(item.getField(), item.getDefaultMessage());
            }
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseCall.error(BizEnum.PARAM_VALID, JSONUtil.toJsonStr(resultMap)));
        }
    }

    public static void printLog(Exception e) {
        HttpServletRequest request = RequestKit.getRequest();
        StringBuffer sb = new StringBuffer();
        assert request != null;
        Map<String, String[]> map = request.getParameterMap();
        if (map != null) {
            map.forEach((k, v) -> sb.append(StrUtil.format("{}：{}", k, v)));
        }
        String body = null;
        try (InputStream is = request.getInputStream()) {

            body = IoUtil.read(is, "utf8");
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        log.error(StrUtil.format("请求地址：{}、请求参数：{}、请求体：{}、异常：{}", request.getRequestURI(), StrUtil.isBlank(sb) ? "无" : sb
                .toString(), StrUtil.isBlank(body) ? "无" : body, ExceptionUtil.stacktraceToString(e)));
    }
}
